# -*- coding: utf-8 -*-
"""
@Time    : 2021/3/19 13:03
@Author  : hui lin
"""
from __future__ import annotations
import math
import os

import psutil
import pygame
import random
import sys
import time

from pygame.event import Event
from pygame.font import Font

from client.model.collide_handler import CollideHandler
from client.model.entity.border import Border
from client.model.entity.brick import Brick
from client.model.entity.home import Home
from client.model.entity.ice import Ice
from client.model.entity.iron import Iron
from client.model.entity.river import River
from client.model.entity.tree import Tree
from client.model.entity_base import Buff
from client.model.gate_data import GateData, GateStatus
from client.model.global_data import GlobalData
from client.model.skill.skill import Skill
from client.model.tank.npc_tank import NpcTank
from client.model.tank.player_tank import PlayerTank
from client.model.entity_manager import entity_mgr
from client.net.conn_manager import NetConnectManager
from client.views.base import ScreenBase, register_screen
from client.config import ClientConfig
from client.views.scene_mgr import scene_mgr

from obespoir.share.espoir_log import global_log as logger

from package.msg_const import MsgID
from package.static.const import UserEvent, TypeIdConst, Faction, MoveDirection
from package.posi import Position
from pygame.surface import Surface
from typing import Optional, List
from package.act_record import ActRecord, ActType
from package.universe import universe

_need_reload = True


@register_screen("battle")
class BattleScreen(ScreenBase):
    KEY_TO_PLAYER = {
        pygame.K_UP: 1,
        pygame.K_DOWN: 1,
        pygame.K_LEFT: 1,
        pygame.K_RIGHT: 1,
    }

    def __init__(self, screen: Surface):
        super(BattleScreen, self).__init__(screen=screen)
        self.config = ClientConfig()
        self.background_img: Surface = pygame.image.load(
            self.config.background_image_path)

        # 是否多人模式
        self.is_multi_mode: bool = False

        self.is_win: bool = False

        # 当局游戏数据
        self.gate_data: Optional[GateData] = None

        # 碰撞处理
        self.collide_handler = CollideHandler(battle_screen=self)

        self.real_time: float = time.time()

        self.frame_font: Font = pygame.font.SysFont("Arial", 12)
        self.frame_font.set_bold(False)
        self.memory_font: Font = pygame.font.SysFont("Arial", 12)
        self.memory_font.set_bold(False)

    @property
    def user_id(self):
        return GlobalData().user_id

    @property
    def frame_sync_rate(self):
        return self.gate_data.frame_sync_rate

    @property
    def frame_physical_rate(self):
        return self.gate_data.frame_physical_rate

    @property
    def cur_physical_frame_no(self):
        return self.gate_data.cur_physical_frame_no

    @property
    def screen_entities(self):
        return entity_mgr.screen_entities

    @property
    def our_tanks(self):
        return entity_mgr.our_tanks

    @property
    def npc_tanks(self):
        return entity_mgr.npc_tanks

    @property
    def our_bullets(self):
        return entity_mgr.our_bullets

    @property
    def npc_bullets(self):
        return entity_mgr.npc_bullets

    @property
    def skills(self):
        return entity_mgr.skills

    def get_player_tank_by_user_id(self, user_id: int) -> PlayerTank:
        for p_tank in self.our_tanks:
            if isinstance(p_tank, PlayerTank) and user_id == p_tank.user_id:
                return p_tank

    def init_screen(self, gate_id: int = 1, mode: bool = False, *args,
                    **kwargs):
        # 清空原来的元素
        entity_mgr.clear()
        self.gate_data = GateData()
        self.gate_data.reset()
        self.gate_data.init_gate(gate_id)
        # 初始化随机种子
        self.gate_data.init_random_seed(121434534)
        width = self.gate_data.GRID_SIZE * self.gate_data.config.width
        height = self.gate_data.GRID_SIZE * self.gate_data.config.height
        screen = pygame.display.set_mode((width, height))
        self._screen = screen
        self._parse_gate_map()

        self.is_multi_mode = mode
        self.is_win = False

        # 场景组件初始化
        self._init_battle_layer()

    async def update(self, events: List[Event] = None, **kwargs):
        """
        :return:
        """
        if self.gate_data.target_frame_no <= self.gate_data.cur_logic_frame_no:  # noqa
            logger.info("reach target frame no, game stopping",
                        cur_frame_no=self.gate_data.cur_logic_frame_no)
            return

        # 追帧（追到当前帧和目标帧）
        gap_num = self.gate_data.target_frame_no - self.gate_data.cur_logic_frame_no    # noqa
        fast_forward_num = max(1, gap_num-self.gate_data.frame_ratio)
        for i in range(fast_forward_num):
            await self.logic_frame_handler(events)

        # 玩家事件捕捉
        await self.handle_player_event(events)

        # 显示实时帧率
        now_time = time.time()
        delta = now_time - self.real_time
        self.real_time = now_time
        frame_physical_rate: float = round(1.0 / delta, 1)
        frame_surface = self.frame_font.render(
            f"FPS: {frame_physical_rate}", True, (255, 255, 255))
        self._screen.blit(frame_surface, (12, 12))

        used_mem = psutil.Process(
            os.getpid()).memory_info().rss / 1024 / 1024
        frame_surface = self.frame_font.render(
            f"MEM: {used_mem:.2f}M", True, (255, 255, 255))
        self._screen.blit(frame_surface, (12, 30))

    async def logic_frame_handler(self, events: List[Event] = None, **kwargs):
        # 关卡控制
        result = self.process_gate_logic()
        if result and -1 != result.get("is_win", -1):
            self.process_round_over(result["is_win"])
        else:
            # 背景填充
            self._screen.fill((0, 0, 0))
            self._screen.blit(self.background_img, (0, 0))

            # 执行逻辑帧事件
            await self.handle_logic_frame()

            # ai处理
            self.ai_handler()

            # 碰撞检测
            self.collide_handler.move_and_check_collide()

            # 地图显示
            self._show_map()

            # 动态元素显示
            self._show_mutable_components()

        if GateStatus.FINISHING == self.gate_data.status:
            self.gate_data.status = GateStatus.FINISHED
            self.real_round_over()

    async def handle_logic_frame(self):
        is_new_logic = self.gate_data.update_frame_no()
        if is_new_logic:
            to_acts: List[ActRecord] = self.gate_data.has_can_do_action()
            # 执行操作
            self._do_act(to_acts)
            self.gate_data.clear_acts(self.gate_data.cur_logic_frame_no)

    def _do_act(self, to_acts: List[ActRecord]):
        for act in to_acts:
            p_tank = self.get_player_tank_by_user_id(act.user_id)
            if ActType.MOVE == act.act_type:
                # 移动
                p_tank.set_move(act.extra_params["directions"])
            elif ActType.SHOOT == act.act_type:
                bullet = p_tank.shoot()
                if bullet:
                    self.our_bullets.add(bullet)
            elif ActType.STOP == act.act_type:
                p_tank.set_move([])

    def ai_handler(self):
        # npc ai
        for npc in self.npc_tanks:
            if isinstance(npc, NpcTank):
                npc.act_ai()

    def _init_battle_layer(self):
        # 中立元素

        # 初始化我方坦克
        for i, u in enumerate(GlobalData().team_users):
            x, y = self.gate_data.config.our_tank_birth[i]
            position = Position(x * self.gate_data.GRID_SIZE,
                                y * self.gate_data.GRID_SIZE)
            tank_player = PlayerTank(user_id=u.user_id,
                                     type_id=TypeIdConst.TANK_LEVEL_1_1,
                                     faction=Faction.OUR_FACTION,
                                     position=position)
            self.our_tanks.add(tank_player)

    def process_gate_logic(self):
        # return
        # 开局后2s生成第一波坦克
        if self.cur_physical_frame_no == self.frame_physical_rate * 2:
            pygame.event.post(Event(UserEvent.GEN_NPC_TANK))
        else:
            # 判断是否需要产生npc
            if self.gate_data.cur_npc_wave_no < self.gate_data.config.waves \
                    and self.gate_data.config.interval * \
                    self.frame_physical_rate == \
                    self.gate_data.cur_physical_frame_no - \
                    self.gate_data.last_npc_frame_no:
                pygame.event.post(Event(UserEvent.GEN_NPC_TANK))

        # 判断是否关卡结束
        if 0 < self.gate_data.cur_logic_frame_no:
            homes = self.screen_entities[TypeIdConst.ENTITY_HOME]
            for home in homes:
                if home and isinstance(home, Home):
                    if home.faction == Faction.OUR_FACTION and home.is_dead:
                        return {"is_win": False}
            player_alive = False
            for p_tank in self.our_tanks:
                if p_tank and isinstance(p_tank,
                                         PlayerTank) and not p_tank.is_dead:
                    player_alive = True
                    break
            if 0 < len(self.our_tanks) and not player_alive:
                return {"is_win": False}

            if self.gate_data.cur_npc_wave_no == len(
                    self.gate_data.config.npc) and 0 == len(self.npc_tanks):
                return {"is_win": True}
        return

    async def _upload_act(self, act_record: ActRecord):
        if self.gate_data.is_net_mode:
            # 联网模式
            await NetConnectManager().send_message(MsgID.GAME_OPERATION,
                                                   act_record.to_dict())
        else:
            # 单机模式
            act_record.frame_no = self.gate_data.cur_logic_frame_no
            self.gate_data.add_to_act(act_record)

    async def handle_player_event(self, events: List[Event] = None):
        if self.gate_data.status != GateStatus.RUNNING:
            return

        for event in events:
            if pygame.QUIT == event.type:
                pygame.quit()
                sys.exit()
            elif UserEvent.GEN_NPC_TANK == event.type:
                # 生成npc坦克
                self.gen_npc_tanks()

        # 按压事件
        press_event = pygame.key.get_pressed()

        directions: List[MoveDirection] = []
        if press_event[pygame.K_UP]:
            # 向上移动
            directions.append(MoveDirection.UP)
        elif press_event[pygame.K_DOWN]:
            directions.append(MoveDirection.DOWN)

        if press_event[pygame.K_LEFT]:
            directions.append(MoveDirection.LEFT)
        elif press_event[pygame.K_RIGHT]:
            directions.append(MoveDirection.RIGHT)

        for p_tank in self.our_tanks:
            logger.debug("events:", guid=GlobalData().user_id,
                         cuid=p_tank.user_id, directions=directions)
            if isinstance(p_tank, PlayerTank) and \
                    GlobalData().user_id == p_tank.user_id:
                # 玩家操作坦克1
                if directions:
                    act_record = ActRecord(act_type=ActType.MOVE,
                                           user_id=p_tank.user_id,
                                           directions=directions)
                    await self._upload_act(act_record=act_record)
                else:
                    if not p_tank.is_stop():
                        act_record = ActRecord(act_type=ActType.STOP,
                                               user_id=p_tank.user_id,
                                               directions=directions)
                        await self._upload_act(act_record=act_record)

                # p_tank.set_move(directions)

        if press_event[pygame.K_SPACE]:
            # 玩家1开火
            for p_tank in self.our_tanks:
                logger.debug("events2:", guid=GlobalData().user_id,
                             cuid=p_tank.user_id, directions=directions)
                if isinstance(p_tank, PlayerTank) and \
                        GlobalData().user_id == p_tank.user_id:
                    # 玩家操作坦克1
                    act_record = ActRecord(act_type=ActType.SHOOT,
                                           user_id=p_tank.user_id)
                    await self._upload_act(act_record=act_record)
                    # bullet = p_tank.shoot()
                    # if bullet:
                    #     self.our_bullets.add(bullet)

    def _get_random_npc_born_position(self, exclude_index: list) -> int:
        num = len(self.gate_data.config.npc_tank_birth)
        indexes = [i for i in range(num) if i not in exclude_index]
        index = random.choice(indexes)
        return index

    def gen_npc_tanks(self):
        self.gate_data.last_npc_frame_no = self.gate_data.cur_logic_frame_no
        logger.info("start", gate_data=self.gate_data)
        t_config = self.gate_data.config.npc[self.gate_data.cur_npc_wave_no]
        for type_id, num in t_config.tank.items():
            for i in range(0, num):
                exclude_index = []
                while 1:
                    index = self._get_random_npc_born_position(
                        exclude_index=exclude_index)
                    x, y = self.gate_data.config.npc_tank_birth[index]
                    tank = NpcTank(type_id=type_id,
                                   direction=MoveDirection.DOWN,
                                   position=Position(
                                       x * self.gate_data.GRID_SIZE,
                                       y * self.gate_data.GRID_SIZE),
                                   gate_id=self.gate_data.gate_id,
                                   cur_wave=self.gate_data.cur_npc_wave_no)
                    if not pygame.sprite.spritecollide(tank, self.npc_tanks,
                                                       False, None) and \
                            not pygame.sprite.spritecollide(tank,
                                                            self.npc_tanks,
                                                            False, None):
                        self.npc_tanks.add(tank)
                        source = "{}".format(self.gate_data.gate_id)
                        rel_ratio = self.gate_data.config.npc_speed_buff
                        buff = Buff(source=source, duration=99999999,
                                    abs_digit=0, rel_ratio=rel_ratio,
                                    is_unique=True)
                        tank.add_buff(buff)
                        break
                    else:
                        exclude_index.append(index)
        self.gate_data.cur_npc_wave_no += 1

    def _show_map(self):
        for _id, group in self.screen_entities.items():
            if len(group):
                group.draw(self._screen)

    def _show_mutable_components(self):
        # 我方坦克
        for tank in self.our_tanks:
            if isinstance(tank, PlayerTank):
                tank.update(self.frame_physical_rate, self._screen)

        # npc坦克
        for tank in self.npc_tanks:
            if isinstance(tank, NpcTank):
                if tank.can_remove():
                    self.npc_tanks.remove(tank)
                else:
                    tank.update(self.frame_physical_rate, self._screen)

        # 子弹展示
        self.our_bullets.draw(self._screen)
        self.npc_bullets.draw(self._screen)

        # 技能展示
        self.skills.draw(self._screen)
        for sk in self.skills:
            if isinstance(sk, Skill):
                sk.tick_by_per_frame()

    def process_round_over(self, is_win: bool):
        logger.info("process_round_over", is_multi_mode=self.is_multi_mode,
                    is_win=is_win, npc_tanks=self.npc_tanks)
        if not self.gate_data.is_net_mode:
            GateData().is_win = is_win
            GateData().status = GateStatus.FINISHING
        else:
            NetConnectManager().send_message(MsgID.GAME_OVER, {
                "is_win": is_win})

    def real_round_over(self):
        cur_gate_id = self.gate_data.gate_id
        self.is_win = GateData().is_win
        if self.is_win and cur_gate_id < universe.max_gate_id:
            scene_mgr.switch_to_scene("switch_gate", mode=self.is_multi_mode,
                                      gate_id=cur_gate_id + 1)
        else:
            scene_mgr.switch_to_scene("game_over", is_win=self.is_win)

    def _parse_gate_map(self):
        path = os.path.join(        # noqa
            ClientConfig().root_dir, self.gate_data.config.path)
        f = open(path, errors="ignore")
        row_no = 0
        max_col_no = 1
        for line in f.readlines():
            line = line.strip('\n')
            for col_no, elem in enumerate(line.split(" ")):
                position = Position(col_no * self.gate_data.GRID_SIZE,
                                    row_no * self.gate_data.GRID_SIZE)
                max_col_no = max(col_no, max_col_no)
                if "B" == elem:
                    obj = Brick(TypeIdConst.ENTITY_BRICK_WALL,
                                faction=Faction.NONE, position=position)
                    self.screen_entities[TypeIdConst.ENTITY_BRICK_WALL].add(
                        obj)
                elif "C" == elem:
                    obj = Ice(TypeIdConst.ENTITY_ICE, faction=Faction.NONE,
                              position=position)
                    self.screen_entities[TypeIdConst.ENTITY_ICE].add(obj)
                elif "H" == elem:
                    obj = Home(TypeIdConst.ENTITY_HOME,
                               faction=Faction.OUR_FACTION, position=position)
                    self.screen_entities[TypeIdConst.ENTITY_HOME].add(obj)
                elif "E" == elem:
                    obj = Home(TypeIdConst.ENTITY_HOME,
                               faction=Faction.ENEMY_FACTION,
                               position=position)
                    self.screen_entities[TypeIdConst.ENTITY_HOME].add(obj)
                elif "I" == elem:
                    obj = Iron(TypeIdConst.ENTITY_IRON_WALL,
                               faction=Faction.NONE, position=position)
                    self.screen_entities[TypeIdConst.ENTITY_IRON_WALL].add(obj)
                elif "R" == elem:
                    obj = River(TypeIdConst.ENTITY_RIVER, faction=Faction.NONE,
                                position=position)
                    self.screen_entities[TypeIdConst.ENTITY_RIVER].add(obj)
                elif "T" == elem:
                    obj = Tree(TypeIdConst.ENTITY_TREE, faction=Faction.NONE,
                               position=position)
                    self.screen_entities[TypeIdConst.ENTITY_TREE].add(obj)

            row_no += 1

        row_no = max(row_no, int(math.ceil(
            self._screen.get_height() / self.gate_data.GRID_SIZE)))
        max_col_no = max(max_col_no, int(math.ceil(
            self._screen.get_width() / self.gate_data.GRID_SIZE)))
        # 生成边界
        width = self._screen.get_width()
        height = self._screen.get_height()
        for i in range(0, row_no):
            # 左边竖排边界
            position = Position(-self.gate_data.GRID_SIZE,
                                i * self.gate_data.GRID_SIZE)
            border = Border(TypeIdConst.ENTITY_BORDER_WALL,
                            faction=Faction.NONE, position=position)
            self.screen_entities[TypeIdConst.ENTITY_BORDER_WALL].add(border)
            # 右边竖排边界
            position = Position(width, i * self.gate_data.GRID_SIZE)
            border = Border(TypeIdConst.ENTITY_BORDER_WALL,
                            faction=Faction.NONE, position=position)
            self.screen_entities[TypeIdConst.ENTITY_BORDER_WALL].add(border)

        for i in range(0, max_col_no):
            # 上边横排边界
            position = Position(i * self.gate_data.GRID_SIZE,
                                -self.gate_data.GRID_SIZE)
            border = Border(TypeIdConst.ENTITY_BORDER_WALL,
                            faction=Faction.NONE, position=position)
            self.screen_entities[TypeIdConst.ENTITY_BORDER_WALL].add(border)
            # 下边横排边界
            position = Position(i * self.gate_data.GRID_SIZE, height)
            border = Border(TypeIdConst.ENTITY_BORDER_WALL,
                            faction=Faction.NONE, position=position)
            self.screen_entities[TypeIdConst.ENTITY_BORDER_WALL].add(border)
